"""
@author: 江同学呀
@file: span_queries.py
@date: 2025/1/13 21:27
@desc:
    Span 查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/span-queries.html

    Span queries are low-level positional queries which provide expert control over the order and proximity of the
    specified terms. These are typically used to implement very specific queries on legal documents or patents.
    It is only allowed to set boost on an outer span query. Compound span queries, like span_near, only use the list of
    matching spans of inner span queries in order to find their own spans, which they then use to produce a score.
    Scores are never computed on inner span queries, which is the reason why boosts are not allowed: they only influence
    the way scores are computed, not spans.
    Span queries cannot be mixed with non-span queries (with the exception of the span_multi query).

    Span 查询是低级位置查询，它提供对指定术语的顺序和接近度的专家控制。这些通常用于对法律文档或专利实施非常具体的查询。
    只允许在 outer span 查询上设置 boost。复合 span 查询与 span_near 一样，仅使用内部 span 查询的匹配 span 列表来查找自己的 span，
    然后使用该 span 生成分数。从不计算内部 span 查询的分数，这就是不允许 boost 的原因：它们只影响分数的计算方式，而不影响 span。
    Span 查询不能与非 Span 查询混合（span_multi 查询除外）。
"""
from typing import Dict, Union, Iterable

from espc.common.common import Number
from espc.err.err import ESPCParamsError
from espc.orm.model.dsl.queries.base_queries import _BaseQueries
from espc.orm.model.mapping.field.base_field.base_field import _BaseField


class _SpanQueries(_BaseQueries):
    """
    Span查询基类
    """
    type: str


class SpanFirst(_SpanQueries):
    """
    Span 第一个查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-first-query.html

    Matches spans near the beginning of a field. The span first query maps to Lucene SpanFirstQuery.
    匹配字段开头附近的跨度。span first 查询映射到 Lucene SpanFirstQuery。

    :param match:
        The match clause can be any other span type query.
        match 子句可以是任何其他 span 类型查询。
    :param end:
        The end controls the maximum end position permitted in a match.
        end 控制对战中允许的最大结束位置。
    :param :
    """
    type: str = "span_first"

    def __init__(self, match: Union[Dict, _SpanQueries], end: int = None):
        super().__init__()
        self._match: Union[Dict, _SpanQueries] = match
        self._end: int = end
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "match": self._match if isinstance(self._match, Dict) else {self._match.type: self._match._build()}
        }
        if self._end is not None:
            _body["end"] = self._end
        return _body


class SpanMultiTerm(_SpanQueries):
    """
    跨度多词查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-multi-term-query.html

    The span_multi query allows you to wrap a multi term query (one of wildcard, fuzzy, prefix, range or regexp query)
    as a span query, so it can be nested.
    span_multi查询允许您将多词查询（通配符、模糊、前缀、范围或正则表达式查询之一）包装为 span 查询，以便可以嵌套。

    :param match:
    """
    type: str = "span_multi"

    def __init__(self, match: Union[Dict, _BaseQueries]):
        super().__init__()
        self._match: Union[Dict, _BaseQueries] = match
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "match": self._match if isinstance(self._match, Dict) else {self._match.type: self._match._build()}
        }
        return _body


class SpanNear(_SpanQueries):
    """
    Span near 查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-near-query.html

    Matches spans which are near one another. One can specify slop, the maximum number of intervening unmatched
    positions, as well as whether matches are required to be in-order. The span near query maps to Lucene SpanNearQuery.
    匹配彼此接近的 span。可以指定 slop、中间不匹配位置的最大数量，以及是否需要按顺序进行匹配。span near 查询映射到 Lucene SpanNearQuery。

    :param clauses:
        The clauses element is a list of one or more other span type queries
        clauses 元素是一个或多个其他 span 类型查询的列表
    :param slop:
        the slop controls the maximum number of intervening unmatched positions permitted.
        slop 控制允许的中间不匹配位置的最大数量。
    :param in_order:
    :param :
    """
    type: str = "span_near"

    def __init__(self, clauses: Iterable[Union[Dict, _SpanQueries]], slop: int = None, in_order: bool = None):
        super().__init__()
        self._clauses: Iterable[Union[Dict, _SpanQueries]] = clauses
        self._slop: int = slop
        self._in_order: bool = in_order
        return

    def _build(self) -> Dict:
        _body: Dict = {
            "clauses": [
                clauses if isinstance(clauses, Dict)
                else {
                    clauses.type: clauses._build()
                }
                for clauses in self._clauses
            ]
        }
        if self._slop is not None:
            _body["slop"] = self._slop
        if self._in_order is not None:
            _body["in_order"] = self._in_order
        return _body


class SpanNot(_SpanQueries):
    """
    Span not 查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-not-query.html

    Removes matches which overlap with another span query or which are within x tokens before (controlled by the
    parameter pre) or y tokens after (controlled by the parameter post) another SpanQuery. The span not query maps to
    Lucene SpanNotQuery.
    删除与另一个 span 查询重叠的匹配项，或者位于另一个 SpanQuery 之前（由参数 pre 控制）或 y 令牌内（由参数 post 控制）的匹配项。span not
    查询映射到 Lucene SpanNotQuery。

    :param include:
        The include and exclude clauses can be any span type query.
        The include clause is the span query whose matches are filtered,

        include 和 exclude 子句可以是任何 span 类型查询。
        include 子句是筛选其匹配项的 span 查询
    :param exclude:
        and the exclude clause is the span query whose matches must not overlap those returned.
        exclude 子句是其匹配项不得与返回的匹配项重叠的 span 查询。
    :param pre:
        If set the amount of tokens before the include span can’t have overlap with the exclude span. Defaults to 0.
        如果设置包含范围之前的令牌数量，则不能与排除范围重叠。默认为 0。
    :param post:
        If set the amount of tokens after the include span can’t have overlap with the exclude span. Defaults to 0.
        如果设置，则 include span 之后的令牌数量不能与 exclude span 重叠。默认为 0。
    :param dist_:
        If set the amount of tokens from within the include span can’t have overlap with the exclude span. Equivalent
        of setting both pre and post.
        如果设置，则 include span 中的令牌数量不能与 exclude span 重叠。相当于同时设置 pre 和 post。
    """
    type: str = "span_not"

    def __init__(self, include: Union[Dict, _SpanQueries] = None, exclude: Union[Dict, _SpanQueries] = None, pre: int = None,
                 post: int = None, dist_: int = None):
        super().__init__()
        self._include: Union[Dict, _SpanQueries] = include
        self._exclude: Union[Dict, _SpanQueries] = exclude
        self._pre: int = pre
        self._post: int = post
        self._dist: int = dist_
        return

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._include:
            _body["include"] = self._include if isinstance(self._include, Dict) else {
                self._include.type: self._include._build()
            }
        if self._exclude:
            _body["exclude"] = self._exclude if isinstance(self._exclude, Dict) else {
                self._exclude.type: self._exclude._build()
            }
        if self._pre is not None:
            _body["pre"] = self._pre
        if self._post is not None:
            _body["post"] = self._post
        return _body


class SpanOr(_SpanQueries):
    """
    Span 或 query
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-or-query.html

    Matches the union of its span clauses. The span or query maps to Lucene SpanOrQuery.
    匹配其 span 子句的 union。span 或 query 映射到 Lucene SpanOrQuery。

    :param clauses:
        The clauses element is a list of one or more other span type queries.
        clauses 元素是一个或多个其他 span 类型查询的列表。
    """
    type: str = "span_or"

    def __init__(self, clauses: Iterable[Union[Dict, _SpanQueries]]):
        super().__init__()
        self._clauses: Iterable[Union[Dict, _SpanQueries]] = clauses
        return

    def _build(self) -> Dict:
        return {
            "clauses": [
                clauses if isinstance(clauses, Dict)
                else {
                    clauses.type: clauses._build()
                }
                for clauses in self._clauses
            ]
        }


class SpanTerm(_SpanQueries):
    """
    Span 术语查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-term-query.html

    Matches spans containing a term. The span term query maps to Lucene SpanTermQuery.
    匹配包含术语的 span。span 术语查询映射到 Lucene SpanTermQuery。

    :param field:
    :param value:
    :param term:
    :param boost:
    """
    type: str = "span_term"

    def __init__(
            self, field: Union[str, _BaseField], value: Union[str, Number] = None, term: Union[str, Number] = None,
            boost: Number = None
    ):
        if value is None and term is None:
            raise ESPCParamsError("value和term不能同时为空。")
        super().__init__()
        self._field: Union[str, _BaseField] = field
        self._value: Union[str, Number] = value if value is not None else term
        self._boost: Number = boost
        return

    def _build(self) -> Dict:
        if self._boost is None:
            return {
                self._field if isinstance(self._field, str) else self._field._field_name: self._value
            }
        _body: Dict = {
            "value": self._value
        }
        if self._boost is not None:
            _body["boost"] = self._boost
        return {
            self._field if isinstance(self._field, str) else self._field._field_name: _body
        }


class SpanWithin(_SpanQueries):
    """
    查询内的跨度
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-within-query.html

    Returns matches which are enclosed inside another span query. The span within query maps to Lucene SpanWithinQuery.
    The big and little clauses can be any span type query. Matching spans from little that are enclosed within big are returned.

    返回包含在另一个 span 查询中的匹配项。查询中的 span 映射到 Lucene SpanWithinQuery。
    子句 big 和 little 可以是任何 span 类型查询。将返回包含在 big 中的 little 的匹配范围。
    :param big:
    :param little:
    """
    type: str = "span_within"

    def __init__(self, big: Union[Dict, _SpanQueries], little: Union[Dict, _SpanQueries]):
        super().__init__()
        self._big: Union[Dict, _SpanQueries] = big
        self._little: Union[Dict, _SpanQueries] = little
        return

    def _build(self) -> Dict:
        return {
            "big": self._big if isinstance(self._big, Dict) else {
                self._big.type: self._big._build()
            },
            "little": self._little if isinstance(self._little, Dict) else {
                self._little.type: self._little._build()
            }
        }


class SpanFieldMasking(_SpanQueries):
    """
    Span 字段掩码查询
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-field-masking-query.html

    Wrapper to allow span queries to participate in composite single-field span queries by lying about their search
    field. The span field masking query maps to Lucene’s SpanFieldMaskingQuery
    Wrapper 允许 span 查询通过对其搜索字段撒谎来参与复合单字段 span 查询。span 字段掩码查询映射到 Lucene 的 SpanFieldMaskingQuery

    This can be used to support queries like span-near or span-or across different fields, which is not ordinarily
    permitted.
    这可用于支持 span-near 或 span-or 跨不同字段的查询，这通常是不允许的。

    Span field masking query is invaluable in conjunction with multi-fields when same content is indexed with multiple
    analyzers. For instance we could index a field with the standard analyzer which breaks text up into words, and again
    with the english analyzer which stems words into their root form.
    当使用多个分析器为同一内容编制索引时，跨度字段掩码查询与多字段结合使用时非常有用。例如，我们可以使用标准分析器为字段编制索引，该分析器将文本分
    解为单词，然后再次使用英语分析器将单词提取为词根形式。

    :param query:
    :param field:
    """
    type: str = "span_field_masking"

    def __init__(self, query: Union[Dict, _SpanQueries], field: Union[str, _BaseField]):
        super().__init__()
        self._query: Union[Dict, _SpanQueries] = query
        self._field: Union[str, _BaseField] = field
        return

    def _build(self) -> Dict:
        return {
            "query": self._query if isinstance(self._query, Dict) else {self._query.type: self._query._build()},
            "field": self._field if isinstance(self._field, str) else self._field._field_name
        }


class SpanContaining(_SpanQueries):
    """
    包含 query 的 Span
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-span-containing-query.html

    Returns matches which enclose another span query. The span containing query maps to Lucene SpanContainingQuery.
    The big and little clauses can be any span type query. Matching spans from big that contain matches from little are
    returned.
    返回包含另一个 span 查询的匹配项。包含查询的 span 映射到 Lucene SpanContainingQuery。
    big 和 little 子句可以是任何 span 类型查询。将返回包含 little 匹配项的 big 匹配范围。

    :param little:
    :param big:
    """
    type: str = "span_containing"

    def __init__(self, little: Union[Dict, _SpanQueries], big: Union[Dict, _SpanQueries]):
        super().__init__()
        self._little: Union[Dict, _SpanQueries] = little
        self._big: Union[Dict, _SpanQueries] = big
        return

    def _build(self) -> Dict:
        return {
            "little": self._little if isinstance(self._little, Dict) else {
                self._little.type: self._little._build()
            },
            "big": self._big if isinstance(self._big, Dict) else {
                self._big.type: self._big._build()
            }
        }












